/**
 * @file hash_table.c
 * Implementation file for a generic HashMap data structure
 *
 * @ingroup HashMap
 */
#include <hash_table.h>

static hash_table_delete_element(hash_table, int);
static void rehash(hash_table);
static int find_entry(hash_table, void*);
static int default_hash_function(int, void*); 
static int default_equal_function(void*, void*); 
static int hash_table_abs(int);

static int hash_table_abs(int v)
{
	return v < 0 ? -v : v;
}

static int default_hash_function(int capacity, void* key)
{
	static int scale = 42; /** TODO : RANDOM GOD, HELP US! */
	static int shift = 42;
	return hash_table_abs((*((int*) key))*scale + shift) % capacity;
}

static int default_equal_function(void* a, void* b)
{
	return *((int*) a) == *((int*) b);
}

hash_table hash_table_new()
{
	hash_table h = (hash_table) malloc(sizeof(struct hash_table));

	h->hash = default_hash_function; /* TODO */
	h->equal = default_equal_function; /* TODO */
	h->del_key = free;
	h->del_value = free;
	h->size = 0;
	h->capacity = HASHTABLE_DEFAULT_CAPACITY;
	h->buckets = (pair**) calloc(h->capacity, sizeof(pair*));

	return h;
}

static hash_table_delete_element(hash_table h, int index)
{
	if(h->buckets[index] != NULL)
	{
		if(h->buckets[index]->first != NULL)
		{
			printf("YoYo : %d %d\n", *((int*)h->buckets[index]->first), *((int*)h->buckets[index]->second));
			h->del_key(h->buckets[index]->first);
			h->del_value(h->buckets[index]->second);
		}
		free(h->buckets[index]);
		h->buckets[index] = NULL;
	}
}

void hash_table_free(hash_table h)
{
	int i;
	for(i = 0; i < h->capacity; i++)
		hash_table_delete_element(h, i);
	free(h->buckets);
	free(h);
}

void hash_table_set_hash_function(hash_table h, hash_function_t f)
{
	h->hash = f;
}

int hash_table_size(hash_table h)
{
	return h->size;
}

int hash_table_is_empty(hash_table h)
{
	return h->size == 0;
}

static void rehash(hash_table h)
{
	int i, new_capacity;
	pair **new_buckets;
	
	new_capacity = h->capacity * 2;
	new_buckets = (pair**) calloc(new_capacity, sizeof(pair*));

	for(i = 0; i < h->capacity; i++)
	{
		if(h->buckets[i] != NULL)
		{
			if(h->buckets[i]->first != NULL)
			{
				int new_hash = h->hash(new_capacity, h->buckets[i]->first);
				new_buckets[new_hash] = h->buckets[i];
			}
			else
			{
				h->del_key(h->buckets[i]->first);
				free(h->buckets[i]);
			}
		}
	}

	free(h->buckets);
	h->capacity = new_capacity;
	h->buckets = new_buckets;
}

static int find_entry(hash_table h, void* key)
{
	int i, index, availiable = HASHTABLE_NOK;
	
	if(key == NULL)
		return HASHTABLE_NOK;

	if(h->size == h->capacity)
		rehash(h);
	
	index = h->hash(h->capacity, key);

	for(i = 0; i < h->capacity; i++)
	{
		if(h->buckets[index] == NULL)
			return index;
		if(h->buckets[index]->first == NULL)
		{
			/** bucket is availiable, remember the position. */
			if(availiable == HASHTABLE_NOK) 
				availiable = index;
		}
		else if(h->equal(h->buckets[index]->first, key))
		{
			return index;
		}
	}

	return availiable;
}

int hash_table_put(hash_table h, void* key, void* value)
{
	int index = find_entry(h, key);

	if(index == HASHTABLE_NOK)
		return HASHTABLE_NOK;
	
	if(h->buckets[index] == NULL)
		h->buckets[index] = (pair*) malloc(sizeof(pair));

	h->buckets[index]->first  = key;
	h->buckets[index]->second = value;
}

pair* hash_table_get_pair(hash_table h, void* key)
{
	int index = find_entry(h, key);

	if(index == HASHTABLE_NOK || h->buckets[index] == NULL || 
		 h->buckets[index]->first == NULL)
		return HASHTABLE_NOK;

	return h->buckets[index];
}

void* hash_table_get(hash_table h, void* key)
{
	return hash_table_get_pair(h, key)->second;
}

void* hash_table_remove(hash_table h, void* key)
{
	void *result;
	int index = find_entry(h, key);

	if(index == HASHTABLE_NOK || h->buckets[index] == NULL || 
		 h->buckets[index]->first == NULL)
		return HASHTABLE_NOK;

	result = h->buckets[index];
	h->del_value(h->buckets[index]->first);
	h->buckets[index] = NULL;

	return result;
}

array_list hash_table_get_keys(hash_table h)
{
	int i;
	array_list keys = array_list_new(h->size, 0, 0, NULL, h->del_key);

	for(i = 0; i < h->capacity; i++)
	{
		if(h->buckets[i] != NULL && h->buckets[i]->first != NULL)
			array_list_add_element(keys, h->buckets[i]->first);
	}

	return keys;
}

array_list hash_table_get_values(hash_table h)
{
	int i;
	array_list values = array_list_new(h->size, 0, 0, NULL, h->del_value);

	for(i = 0; i < h->capacity; i++)
	{
		if(h->buckets[i] != NULL && h->buckets[i]->first != NULL)
			array_list_add_element(values, h->buckets[i]->second);
	}

	return values;
}

